﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Collections;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Data.Odbc;
using System.Data.OleDb;
using System.Configuration;
using System.IO;
using System.Xml;

namespace PATBController
{
    /*
     * Manages Test Machines
    */
    public class TestMachineManagerClass
    {
        public Dictionary<string, VirtualMachineServer> VirtualServers { get; set; }
        public Dictionary<int, TestMachine> TestMachines { get; set; }

        // Initialize
        public TestMachineManagerClass()
        {
            VirtualServers = new Dictionary<string, VirtualMachineServer>();
            TestMachines = new Dictionary<int, TestMachine>();
            Refresh(1);
        }
        public int GetTestMachineCount()
        {
            return (TestMachines.Count);
        }
        public int GetTestMachineActiveCount()
        {
            int iActive = 0;
            foreach (TestMachine oItem in TestMachines.Values)
                if (oItem.IsActive)
                    iActive++;
            return (iActive);
        }
        public int GetTestMachineInUseCount()
        {
            int iInUse = 0;
            foreach (TestMachine oItem in TestMachines.Values)
                if (oItem.InUse)
                    iInUse++;
            return (iInUse);
        }
        public int GetVirtualServerMaxConnectionCount()
        {
            int iVal = 0;
            foreach (VirtualMachineServer oItem in VirtualServers.Values)
                iVal += oItem.MaxConnections;
            return (iVal);
        }
        public int GetVirtualServerActiveConnectionCount()
        {
            int iVal = 0;
            foreach (VirtualMachineServer oItem in VirtualServers.Values)
                iVal += oItem.ActiveConnections;
            return (iVal);
        }
        public bool CheckAvailable(int TestMachineID)
        {
            if (TestMachines.ContainsKey(TestMachineID))
            {
                TestMachine oMachine = TestMachines[TestMachineID];
                if (oMachine.IsActive && (!oMachine.InUse) && (!oMachine.PendingPrep))
                {
                    if (oMachine.IsVirtal)
                    {
                        string sVMHost = oMachine.VirtualServerHost;
                        if (VirtualServers[sVMHost].ActiveConnections < VirtualServers[sVMHost].MaxConnections)
                            if(VirtualServers[sVMHost].NextAvailable < DateTime.Now)
                                return (true);
                    }
                    else
                        return (true);
                }
            }
            return (false);
        }
        public bool CheckAvailable(List<int> TestMachineIDs)
        {
            return (GetAvailable(TestMachineIDs) != -1);
        }
        public int GetAvailable(List<int> TestMachineIDs)
        {
            foreach (int TestMachineID in TestMachineIDs)
            {
                if (CheckAvailable(TestMachineID))
                    return (TestMachineID);
            }
            return (-1);
        }
        public bool IsVirtual(int TestMachineID)
        {
            return (TestMachines[TestMachineID].IsVirtal);
        }
        public bool CheckPendingReady( int TestMachineID )
        {
            if (TestMachines[TestMachineID].NextCheck < DateTime.Now)
            {
                TestMachines[TestMachineID].NextCheck = DateTime.Now.AddSeconds(10);
                return (true);
            }
            return (false);
        }
        public bool SetUse(int TestMachineID, DefTestCase oTest, bool bInUse, int iLogIndent)
        {
            if (TestMachines.ContainsKey(TestMachineID))
            {
                if (TestMachines[TestMachineID].InUse == bInUse)
                    return (false);
                if (TestMachines[TestMachineID].IsVirtal)
                {
                    if (bInUse)
                    {
                        try
                        {
                            if (PrepVirtualMachine(TestMachines[TestMachineID], iLogIndent + 1))
                            {
                                oTest.AsssignedTestMachine = TestMachineID;
                                VirtualServers[TestMachines[TestMachineID].VirtualServerHost].ActiveConnections--;
                                return (true);
                            }
                            else
                                return (false);
                        }
                        catch (Exception ex)
                        {
                            Logger.Log(iLogIndent, "TestMachineManager.SetUse", "*Error activating virtual test machine: " + ex.Message);
                            return (false);
                        }
                    }
                    else
                    {
                        try
                        {
                            ReleaseVirtualMachine(TestMachines[TestMachineID], iLogIndent + 1);
                        }
                        catch (Exception ex)
                        {
                            Logger.Log(iLogIndent, "TestMachineManager.SetUse", "*Error activating virtual test machine: " + ex.Message);
                            oTest.AssignedIp = "";
                            return (false);
                        }
                        VirtualServers[TestMachines[TestMachineID].VirtualServerHost].ActiveConnections--;
                        TestMachines[TestMachineID].InUse = false;
                        oTest.AssignedIp = "";
                    }
                }
                else
                {
                    if (bInUse)
                    {
                        oTest.AssignedIp = TestMachines[TestMachineID].Address;
                        TestMachines[TestMachineID].InUse = true;
                        oTest.AsssignedTestMachine = TestMachineID;
                    }
                    else
                    {
                        oTest.AssignedIp = "";
                        TestMachines[TestMachineID].InUse = false;
                    }
                }
            }
            return (true);
        }
        public int SetInUse_Finalize(int TestMachineID, DefTestCase oTest, int iLogIndent)
        {
            try
            {
                string sResult = PrepVirtualMachine_Finalize(TestMachines[TestMachineID], iLogIndent + 1);
                if (sResult == "FAILED")
                {
                    VirtualServers[TestMachines[TestMachineID].VirtualServerHost].ActiveConnections--;
                    return (-1);
                }
                if (sResult == "")
                    return (0);
                oTest.AssignedIp = sResult;
            }
            catch (Exception ex)
            {
                Logger.Log(iLogIndent, "TestMachineManager.SetUse", "*Error activating virtual test machine: " + ex.Message);
                return (-1);
            }
            TestMachines[TestMachineID].InUse = true;
            oTest.TestMachineUsed = "Virtual Machine Host "
                + TestMachines[TestMachineID].VirtualServerHost
                + ": "
                + TestMachines[TestMachineID].Address;
            return (1);
        }
        public void Refresh(int iLogIndent)
        {
            Dictionary<int, TestMachine> collTM = new Dictionary<int, TestMachine>();
            if(VirtualServers.Count > 0)
            {
                foreach (VirtualMachineServer oVM in VirtualServers.Values)
                {
                    oVM.MaxConnections = 0;
                }
            }
            using (ATBSchemaDB objATBDB = new ATBSchemaDB())
            {
                DataTable dtList;
                dtList = objATBDB.GetDefTestMachineList();

                for (int iIndex = 0; iIndex < dtList.Rows.Count; iIndex++)
                {
                    string sVal;
                    sVal = dtList.Rows[iIndex]["DefTestMachinesID"].ToString();
                    int iKey = Convert.ToInt32(sVal);
                    sVal = dtList.Rows[iIndex]["IsVirtual"].ToString();
                    bool bIsVirtual = (sVal.ToLower() == "true");
                    sVal = dtList.Rows[iIndex]["Active"].ToString();
                    bool bIsActive = (sVal.ToLower() == "true");
                    if (TestMachines.ContainsKey(iKey))
                    {
                        if (bIsVirtual)
                            TestMachines[iKey].Update(
                                    dtList.Rows[iIndex]["VirtualName"].ToString(),
                                    bIsActive,
                                    bIsVirtual,
                                    dtList.Rows[iIndex]["Location"].ToString()
                                );
                        else
                            TestMachines[iKey].Update(
                                    dtList.Rows[iIndex]["Location"].ToString(),
                                    bIsActive,
                                    bIsVirtual,
                                    ""
                                );
                        if (bIsActive && TestMachines[iKey].IsDeleted)
                            TestMachines[iKey].IsDeleted = false;
                    }
                    else
                    {
                        if (bIsVirtual)
                        {
                            TestMachines.Add(
                                iKey,
                                new TestMachine(
                                    iKey,
                                    dtList.Rows[iIndex]["VirtualName"].ToString(),
                                    bIsActive,
                                    bIsVirtual,
                                    dtList.Rows[iIndex]["Location"].ToString()
                                    )
                                );
                        }
                        else
                            TestMachines.Add(
                                iKey,
                                new TestMachine(
                                    iKey,
                                    dtList.Rows[iIndex]["Location"].ToString(),
                                    bIsActive
                                    )
                                );
                        if(bIsVirtual)
                            Logger.Log(iLogIndent, "RunsManager", "-Added virtual test machine Key==" 
                                    + iKey.ToString() 
                                    + "; Host==" + TestMachines[iKey].VirtualServerHost
                                    + "; IsActive==" + TestMachines[iKey].IsActive.ToString()
                                    + "; VM==" + TestMachines[iKey].Address
                                    );
                        else
                            Logger.Log(iLogIndent, "RunsManager", "-Added test machine Key=="
                                    + iKey.ToString()
                                    + "; Address==" + TestMachines[iKey].Address
                                    + "; IsActive==" + TestMachines[iKey].IsActive.ToString()
                                    );
                    }
                    // Check virtual host
                    if (bIsVirtual)
                    {
                        string sVMHost = TestMachines[iKey].VirtualServerHost;
                        if (!VirtualServers.ContainsKey(sVMHost))
                        {
                            VirtualServers.Add(
                                sVMHost,
                                new VirtualMachineServer
                                {
                                    ActiveConnections = 0,
                                    Host = TestMachines[iKey].VirtualServerHost,
                                    MaxConnections = (bIsActive ? int.Parse(dtList.Rows[iIndex]["VirtualMaxGuests"].ToString()) : 0)
                                });
                            Logger.Log(iLogIndent, "RunsManager", "-Added virtual host"
                                    + " Host==" + sVMHost
                                    + "; MaxGuests==" + VirtualServers[sVMHost].MaxConnections.ToString()
                                    );
                        }
                        else
                        {
                            if (bIsActive)
                            {
                                int iMax = int.Parse(dtList.Rows[iIndex]["VirtualMaxGuests"].ToString());
                                if (iMax < VirtualServers[sVMHost].MaxConnections || VirtualServers[sVMHost].MaxConnections == 0)
                                    VirtualServers[sVMHost].MaxConnections = iMax;
                            }
                        }
                    }

                    // Add to temp collection
                    collTM.Add(iKey, TestMachines[iKey]);
                }
            }
            // check for any deleted records and update accordingly
            foreach (TestMachine oTM in collTM.Values)
            {
                if (!collTM.ContainsKey(oTM.Key))
                {
                    Logger.Log(iLogIndent, "RunsManager", "-Test machine orphan found, removing; Key==" + oTM.Key.ToString());
                    oTM.SetDeleted();
                }
            }
            // update vm host inuse count
            foreach (VirtualMachineServer oVM in VirtualServers.Values)
            {
                oVM.ActiveConnections = 0;
            }
            foreach (TestMachine oTM in collTM.Values)
            {
                if ((oTM.InUse || oTM.PendingPrep) && oTM.IsVirtal)
                    if (VirtualServers.ContainsKey(oTM.VirtualServerHost))
                        VirtualServers[oTM.VirtualServerHost].ActiveConnections++;
            }
        }
        public bool PrepVirtualMachine(TestMachine oMachine, int iLogIndent)
        {
            bool bSuccess = false;
            try
            {
                string sStartupDelay = ConfigurationManager.AppSettings["vmStartupDelayMins"];
                string sMaxStartupTime = ConfigurationManager.AppSettings["vmMaxWaitMins"];
                string sMaxTries = ConfigurationManager.AppSettings["vmMaxTries"];
                string sVmHostUser = ConfigurationManager.AppSettings["VmUser"];
                string sVmHostPass = ConfigurationManager.AppSettings["VmPass"];
                string sVmSnapshotName = ConfigurationManager.AppSettings["vmSnapshotName"];

                DateTime dtStartTime = DateTime.Now;
                DateTime dtMaxTime = dtStartTime.AddMinutes(Convert.ToDouble(sMaxStartupTime));

                int iMaxTries = Convert.ToInt32(sMaxTries);
                int iTries = iMaxTries;
                while (iTries-- > 0)
                {
                    using (VMwareESXiHandler vmHandler = new VMwareESXiHandler(oMachine.VirtualServerHost))
                    {
                        Logger.Log(iLogIndent++, "TestMachineManager.PrepVirtualMachine", "-Prepping virtual machine " + oMachine.Address + " on host " + oMachine.VirtualServerHost);
                        Logger.Log(iLogIndent, "TestMachineManager.PrepVirtualMachine", ">Try " + (iMaxTries-iTries).ToString() + " of " + iMaxTries.ToString());
                        Logger.Log(iLogIndent, "TestMachineManager.PrepVirtualMachine", ">Connecting to host");
                        vmHandler.Connect(sVmHostUser, sVmHostPass);

                        vmHandler.SelectMachine(oMachine.Address);
                        Logger.Log(iLogIndent, "TestMachineManager.PrepVirtualMachine", ">Current machine state: " + vmHandler.machineProperties["PowerState"]);

                        Logger.Log(iLogIndent, "TestMachineManager.PrepVirtualMachine", ">Reverting to snapshot " + sVmSnapshotName);
                        vmHandler.RevertToSnapshot(sVmSnapshotName);

                        Logger.Log(iLogIndent, "TestMachineManager.PrepVirtualMachine", ">Waiting for revert operation to complete");
                        // wait for esx to complete the operation
                        Thread.Sleep(3000);
                        while (vmHandler.PowerState() != vmHandler.VMPOWERSTATE_POWEREDOFF)
                        {
                            Thread.Sleep(1000);

                            if (DateTime.Now > dtMaxTime)
                                throw new Exception("Maximum time exceeded for vm prep");
                        }
                        // power on the machine
                        Logger.Log(iLogIndent, "TestMachineManager.PrepVirtualMachine", ">Powering on machine");
                        vmHandler.PowerOn();
                        Logger.Log(iLogIndent, "TestMachineManager.PrepVirtualMachine", ">Waiting for power-on operation to complete");
                        while (vmHandler.PowerState() != vmHandler.VMPOWERSTATE_POWEREDON)
                        {
                            Thread.Sleep(1000);

                            if (DateTime.Now > dtMaxTime)
                                throw new Exception("Maximum time exceeded for vm prep");
                        }

                        // wait for vmware tools to initialize
                        Logger.Log(iLogIndent, "TestMachineManager.PrepVirtualMachine", ">Waiting " + sStartupDelay + " mins for vm to startup");

                        oMachine.PendingPrep = true;
                        oMachine.PrepExpiration = dtMaxTime;
                        oMachine.NextCheck = DateTime.Now.AddMinutes(int.Parse(sStartupDelay));
                        bSuccess = true;
                        break;
                    }

                }
            }
            catch(Exception ex)
            {
                Logger.Log(iLogIndent, "TestMachineManager.PrepVirtualMachine", "*Exception: " + ex.Message);
            }
            return (bSuccess);
        }
        public string PrepVirtualMachine_Finalize(TestMachine oMachine, int iLogIndent)
        {
            try
            {
                string sMaxStartupTime = ConfigurationManager.AppSettings["vmMaxWaitMins"];
                string sVmHostUser = ConfigurationManager.AppSettings["VmUser"];
                string sVmHostPass = ConfigurationManager.AppSettings["VmPass"];

                DateTime dtMaxTime = oMachine.PrepExpiration;

                using (VMwareESXiHandler vmHandler = new VMwareESXiHandler(oMachine.VirtualServerHost))
                {
                    Logger.Log(iLogIndent++, "TestMachineManager.PrepVirtualMachine", "-Checking startup status of " + oMachine.Address + " on host " + oMachine.VirtualServerHost);
                    Logger.Log(iLogIndent, "TestMachineManager.PrepVirtualMachine", ">Connecting to host");
                    vmHandler.Connect(sVmHostUser, sVmHostPass);

                    vmHandler.SelectMachine(oMachine.Address);
                    Logger.Log(iLogIndent, "TestMachineManager.PrepVirtualMachine", ">Current machine state: " + vmHandler.machineProperties["PowerState"]);

                    // wait for vmware tools to initialize
                    Logger.Log(iLogIndent, "TestMachineManager.PrepVirtualMachine", ">Checking vmware tools initialized state");
                    if (vmHandler.PowerState() == vmHandler.VMPOWERSTATE_POWEREDON)
                    {
                        if (vmHandler.assignedIp == "")
                        {
                            Logger.Log(iLogIndent, "TestMachineManager.PrepVirtualMachine", "-Wait time elapsed (seconds): "
                                + DateTime.Now.Subtract(dtMaxTime.AddMinutes(-(Convert.ToDouble(sMaxStartupTime)))).TotalSeconds.ToString("N0")
                                + " ; Max wait: " + (int.Parse(sMaxStartupTime) * 60).ToString());
                            if (DateTime.Now > dtMaxTime)
                                throw new Exception("Maximum time exceeded for vm prep");
                        }
                        else
                        {
                            Logger.Log(iLogIndent, "TestMachineManager.PrepVirtualMachine", ">Completed in (seconds): " 
                                + DateTime.Now.Subtract(dtMaxTime.AddMinutes(-(Convert.ToDouble(sMaxStartupTime)))).TotalSeconds.ToString("N0"));
                            oMachine.PendingPrep = false;
                            return (vmHandler.assignedIp);
                        }
                    }
                    else
                    {
                        throw new Exception("Vm prep failed - machine didn't stay powered on");
                    }


                }

            }
            catch (Exception ex)
            {
                Logger.Log(iLogIndent, "TestMachineManager.PrepVirtualMachine", "*Exception: " + ex.Message);
                oMachine.PendingPrep = false;
                return ("FAILED");
            }
            return ("");
        }
        public void InitVirtualMachines(int iLogIndent)
        {
            foreach( TestMachine oMachine in TestMachines.Values)
            {
                if (oMachine.IsActive && !oMachine.InUse)
                    ReleaseVirtualMachine(oMachine, iLogIndent);
            }
        }
        public void ReleaseVirtualMachine(TestMachine oMachine, int iLogIndent)
        {
            string sStabilizationDelay = ConfigurationManager.AppSettings["vmStabilizationDelayMins"];
            string sMaxStartupTime = ConfigurationManager.AppSettings["vmMaxWaitMins"];
            string sMaxTries = ConfigurationManager.AppSettings["vmMaxTries"];
            string sVmHostUser = ConfigurationManager.AppSettings["VmUser"];
            string sVmHostPass = ConfigurationManager.AppSettings["VmPass"];

            DateTime dtStartTime = DateTime.Now;
            DateTime dtMaxTime = dtStartTime.AddMinutes(Convert.ToDouble(sMaxStartupTime));

            int iMaxTries = Convert.ToInt32(sMaxTries);
            int iTries = iMaxTries;

            while (iTries-- > 0)
            {
                try
                {
                    using (VMwareESXiHandler vmHandler = new VMwareESXiHandler(oMachine.VirtualServerHost))
                    {
                        Logger.Log(iLogIndent++, "TestMachineManager.ReleaseTestMachine", "-Releasing virtual machine " + oMachine.Address + " on host " + oMachine.VirtualServerHost);
                        Logger.Log(iLogIndent, "TestMachineManager.ReleaseTestMachine", ">Try " + (iMaxTries - iTries).ToString() + " of " + iMaxTries.ToString());
                        Logger.Log(iLogIndent, "TestMachineManager.ReleaseTestMachine", ">Connecting to host");
                        vmHandler.Connect(sVmHostUser, sVmHostPass);

                        vmHandler.SelectMachine(oMachine.Address);
                        string sPowerState = vmHandler.machineProperties["PowerState"];
                        Logger.Log(iLogIndent, "TestMachineManager.ReleaseTestMachine", ">Current machine state: " + sPowerState);

                        if (!sPowerState.Equals("poweredOff"))
                        {
                            VirtualServers[oMachine.VirtualServerHost].NextAvailable = DateTime.Now.AddMinutes(int.Parse(sStabilizationDelay));

                            Logger.Log(iLogIndent, "TestMachineManager.ReleaseTestMachine", ">Powering down");
                            vmHandler.PowerOff();

                            Logger.Log(iLogIndent, "TestMachineManager.ReleaseTestMachine", ">Waiting for operation to complete");
                            // wait for esx to complete the operation
                            Thread.Sleep(3000);
                            while (vmHandler.PowerState() != vmHandler.VMPOWERSTATE_POWEREDOFF)
                            {
                                Thread.Sleep(1000);

                                if (DateTime.Now > dtMaxTime)
                                    throw new Exception("Maximum time exceeded for vm release");
                            }

                            Logger.Log(iLogIndent, "TestMachineManager.ReleaseTestMachine", ">Completed in (seconds) " + DateTime.Now.Subtract(dtStartTime).TotalSeconds.ToString("N0"));
                        }
                        else
                        {
                            Logger.Log(iLogIndent, "TestMachineManager.ReleaseTestMachine", ">Machine already off, nothing to do.");
                            if (VirtualServers[oMachine.VirtualServerHost].NextAvailable < DateTime.Now )
                                VirtualServers[oMachine.VirtualServerHost].NextAvailable = DateTime.Now;
                        }
                        Logger.Log(iLogIndent, "TestMachineManager.ReleaseTestMachine", ">Next available: " + VirtualServers[oMachine.VirtualServerHost].NextAvailable.ToString());
                        return;
                    }
                }
                catch (Exception ex)
                {
                    Logger.Log(iLogIndent, "TestMachineManager.ReleaseTestMachine", "*Exception: " + ex.Message);
                }
                
            }
        }
    }

    public class VirtualMachineServer
    {
        public string Host { get; set; }
        public int MaxConnections { get; set; }
        public int ActiveConnections { get; set; }
        public DateTime NextAvailable { get; set; }
    }
    public class TestMachine
    {
        public int Key { get; set; }
        public string Address { get; set; }
        public bool IsActive { get; set; }
        public bool InUse { get; set; }
        public bool PendingPrep { get; set; }
        public DateTime PrepExpiration { get; set; }
        public DateTime NextCheck { get; set; }
        public bool IsVirtal { get; set; }
        public string VirtualServerHost { get; set; }
        public bool IsDeleted { get; set; }
        public TestMachine()
        {
            IsActive = false;
            IsVirtal = false;
            InUse = false;
            IsDeleted = false;
            PendingPrep = false;
        }
        public TestMachine(int iKey, string sAddress, bool bIsActive, bool bIsVirtual, string sVirtualServerHost)
        {
            Key = iKey;
            Address = sAddress;
            IsActive = bIsActive;
            IsVirtal = bIsActive;
            VirtualServerHost = sVirtualServerHost;
            InUse = false;
            IsDeleted = false;
            PendingPrep = false;
        }
        public TestMachine(int iKey, string sAddress, bool bIsActive)
        {
            Key = iKey;
            Address = sAddress;
            IsActive = bIsActive;
            IsVirtal = false;
            VirtualServerHost = "";
            InUse = false;
            IsDeleted = false;
            PendingPrep = false;
        }
        public void Update(string sAddress, bool bIsActive, bool bIsVirtual, string sVirtualServerHost)
        {
            Address = sAddress;
            IsActive = bIsActive;
            IsVirtal = bIsActive;
            VirtualServerHost = sVirtualServerHost;
        }
        public void SetDeleted()
        {
            IsActive = false;
            IsDeleted = true;
        }
    }

}
